package com.pan.framework.core.aop;

import com.pan.framework.anno.aop.MyAfter;
import com.pan.framework.anno.aop.MyAspect;
import com.pan.framework.anno.aop.MyBefore;
import com.pan.framework.core.container.MyContainer;
import com.pan.framework.core.factory.scan.MyScan;
import com.pan.framework.proxy.MyInvocationHandler;
import com.pan.framework.entity.BeanInfo;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class MyAopProxyFactory {

    private MyContainer container;
    private MyScan scan;

    public MyAopProxyFactory(MyContainer container, MyScan scan) {
        this.container = container;
        this.scan = scan;
    }


    public void aspectHandler(List<String> aspectName) throws Exception {
        for (String s : aspectName) {
            aspectHandler(s);
        }
    }

    public void aspectHandler(String aspectName) throws Exception {
        BeanInfo beanInfo = container.getBean(aspectName);
        Class aspectClazz = beanInfo.getClazz();
        Object aspect = beanInfo.getBean();
        if (!aspectClazz.isAnnotationPresent(MyAspect.class)) {
            return;
        }
        Method[] methods = aspectClazz.getMethods();
        for (Method method : methods) {
            String executionStr = "";
            if (method.isAnnotationPresent(MyBefore.class)) {
                executionStr = method.getAnnotation(MyBefore.class).execution();
            } else if (method.isAnnotationPresent(MyAfter.class)) {
                executionStr = method.getAnnotation(MyAfter.class).execution();
            } else {
                continue;
            }
            createProxy(executionStr, method, aspect);
        }

    }


    public boolean hasMethod(Class clazz, String methodName, Class[] paramType) {
        l0:
        for (Method method : clazz.getMethods()) {
            if (method.getName().equals(methodName)) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length != paramType.length) {
                    continue l0;
                }
                for (int i = 0; i < parameterTypes.length; i++) {
                    if (parameterTypes[i] != paramType[i]) {
                        continue l0;
                    }
                }
                return true;
            }
        }
        return false;

    }


    //ex:
    //  execution="BeanName@MethodName(ParamClass,ParamClass, ...);"
    public void createProxy(String executionStr, Method aspectMethod, Object aspect) throws Exception {
        if (aspectMethod.getParameterTypes().length > 1) {
            throw new RuntimeException(aspectMethod.getName() + "参数错误");
        }

        String[] targetArr = executionStr.strip().split(";");
        l0:
        for (String s : targetArr) {
            String[] targetBeanNameAndMore = s.strip().split("@");
            String targetBeanName = targetBeanNameAndMore[0];
            String[] targetMethodNameAndMore = targetBeanNameAndMore[1].strip()
                    .substring(0, targetBeanNameAndMore[1].length() - 1).strip().split("\\(");

            String targetMethodName = targetMethodNameAndMore[0];
            Class[] paramClassArr = null;
            if(targetMethodNameAndMore.length==1){
                paramClassArr = new Class[0];
            } else if (targetMethodNameAndMore.length==2){
                String[] paramClassStrArr = targetMethodNameAndMore[1].split(",");
                paramClassArr = new Class[paramClassStrArr.length];
                for (int i = 0; i < paramClassArr.length; i++) {
                    paramClassArr[i] = Class.forName(paramClassStrArr[i]);
                }
            }
            if (!container.exists(targetBeanName)) {
                throw new RuntimeException("代理的对象不存在："+s);
//                continue l0;
            }
            BeanInfo targetBeanInfo = container.getBean(targetBeanName);
            Object targetBean = targetBeanInfo.getBean();
            Class targetClazz = targetBeanInfo.getClazz();
            if (!this.hasMethod(targetClazz, targetMethodName, paramClassArr)) {
                continue l0;
            }
            Method targetMethod = targetClazz.getMethod(targetMethodName, paramClassArr);
            MyInvocationHandler invocationHandler =
                    new MyAopInvocationHandler(targetMethod,targetBean,aspect,aspectMethod);
            Object obj = Proxy.newProxyInstance(
                    scan.getMyLoader(),
                    targetBean.getClass().getInterfaces(),
                    invocationHandler);
            targetBeanInfo.setBean(obj);

        }
    }

}